﻿using System.Text;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Serialization;
using Elderos.Utils.Logging;

namespace Elderos.Utils
{
	/// <summary>
	/// Обобщенный класс для работы с интервалами.
	/// </summary>
	/// <typeparam name="T">Тип элемента интервала.</typeparam>
	[Serializable]
	public class Interval<T> : IEquatable<Interval<T>>
		where T : IComparable<T>
	{
		#region Конструктор

		/// <summary>
		/// Создать новый интервал.
		/// </summary>
		/// <param name="first">Нижний предел интервала.</param>
		/// <param name="last">Верхний предел интервала.</param>
		public Interval(T first, T last)
		{
			Logger.Assert(() => last.CompareTo(first) >= 0, "Interval last is more than interval first.");
			_first = first;
			_last = last;
		}

		public Interval() {}

		#endregion

		#region Общедоступные свойства

		/// <summary>
		/// Нижний предел интервала.
		/// </summary>
		public T First
		{
			get { return _first; }
			set { _first = value; }
		}

		/// <summary>
		/// Верхний предел интервала.
		/// </summary>
		public T Last
		{
			get { return _last; }
			set { _last = value; }
		}

		[XmlIgnore]
		public int Length
		{
			get
			{
				if (typeof(T) == typeof(int))
					return Convert.ToInt32(_last) - Convert.ToInt32(_first) + 1;
				return -1;
			}
		}

		#endregion

		#region Общедоступные методы

		public override string ToString()
		{
			return String.Format("[{0};{1}]", First, Last);
		}

		/// <summary>
		/// Проверяет, принадлежит ли значение интервалу.
		/// </summary>
		/// <param name="value">Значение.</param>
		/// <returns>true - если принадлежит, false - если не принадлежит.</returns>
		public bool Contains(T value)
		{
			return (value.CompareTo(_first) >= 0 && value.CompareTo(_last) <= 0);
		}

		/// <summary>
		/// Проверяет, содержится ли полностью интервал <paramref name="value"/> в текущем интервале.
		/// </summary>
		/// <param name="value">Интервал.</param>
		/// <returns>true - если содержится, false - если не содержится.</returns>
		public bool Contains(Interval<T> value)
		{
			return (value.Last.CompareTo(value.First) >= 0 && Contains(value.First) && Contains(value.Last));
		}

		/// <summary>
		/// Проверяет, имеется ли пересечение (общие значения) у текущего интервала с интервалом <paramref name="value"/>.
		/// </summary>
		/// <param name="value">Интервал.</param>
		public bool IsIntersection(Interval<T> value)
		{
			return
				(_first.CompareTo(value.First) > 0 ? _first : value.First).CompareTo(_last.CompareTo(value.Last) < 0
																				 ? _last
																				 : value.Last) <= 0;
		}

		/// <summary>
		/// Проверяет, имеется ли пересечение (общие значения) у текущего интервала с набором интервалов <paramref name="values"/>.
		/// </summary>
		/// <param name="values">Набор интервалов.</param>
		public bool IsIntersection(IEnumerable<Interval<T>> values)
		{
			IEnumerator<Interval<T>> value = values.GetEnumerator();
			while (value.MoveNext())
			{
				if (IsIntersection(value.Current))
					return true;
			}
			return false;
		}

		/// <summary>
		/// Находит пересечение интервала <paramref name="value"/> с текущим интервалом.
		/// </summary>
		/// <param name="value">Интервал.</param>
		/// <returns>Если интервалы имеют общие точки, то интервал, иначе - null.</returns>
		public Interval<T> Intersect(Interval<T> value)
		{
			T tmin = _first.CompareTo(value.First) > 0 ? _first : value.First;
			T tmax = _last.CompareTo(value.Last) < 0 ? _last : value.Last;
			return tmin.CompareTo(tmax) <= 0 ? new Interval<T>(tmin, tmax) : null;
		}

		/// <summary>
		/// Находит пересечение набора интервалов <paramref name="values"/> с текущим интервалом.
		/// </summary>
		/// <param name="values">Интервал.</param>
		/// <returns>Если интервалы имеют общие точки, то интервал, иначе - null.</returns>
		/// <remarks>Уплотнение интервалов в этом методе не производится.</remarks>
		public List<Interval<T>> Intersect(IEnumerable<Interval<T>> values)
		{
			return (from interval in values
					where IsIntersection(interval)
					select Intersect(interval)).ToList();
		}

		/// <summary>
		/// Проверяет, имеется ли объединение (общие значения) у текущего интервала с нтервалом <paramref name="value"/>.
		/// То же, что и метод IsIntersection.
		/// </summary>
		/// <param name="value">Набор интервалов.</param>
		public bool IsCombination(Interval<T> value)
		{
			return (IsIntersection(value));
		}

		/// <summary>
		/// Объединяет набор интервалов value с текущим интервалом.
		/// </summary>
		/// <param name="value">Интервал.</param>
		/// <returns>Если интервалы имеют общие точки, то интервал, иначе - null.</returns>
		public Interval<T> Combine(Interval<T> value)
		{
			return IsCombination(value)
					   ? new Interval<T>(_first.CompareTo(value.First) < 0 ? _first : value.First,
										 _last.CompareTo(value.Last) > 0 ? _last : value.Last)
					   : null;
		}

		/// <summary>
		/// Объединяет набор интервалов <paramref name="values"/> с текущим интервалом.
		/// Если, в результате объединения, из нескольких интервалов из получившегося набора можно образовать один, 
		/// содержащий эти интервалы, то эти интервалы будут объединены в один интервал.
		/// </summary>
		/// <param name="values">Набор интервалов.</param>
		/// <returns>Набор интервалов.</returns>
		public List<Interval<T>> Combine(List<Interval<T>> values)
		{
			values.Add(this);
			values.Sort(SorterByMin);
			Interval<T> cur = this;
			var curValues = new List<Interval<T>>(values.Count);
			bool wasCombine = false;

			foreach (var t in values)
			{
				wasCombine = cur.IsCombination(t);
				if (wasCombine)
					cur = cur.Combine(t);
				else
				{
					curValues.Add(cur);
					cur = t;
				}
			}
			if (wasCombine)
				curValues.Add(cur);

			curValues.TrimExcess();
			return curValues;
		}

		/// <summary>
		/// Исключает текущий интервал из интервала <paramref name="value"/>.
		/// </summary>
		/// <param name="value">Набор интервалов.</param>
		/// <returns>Если интервалы имеют общие точки, то набор интервалов из 1 или 2 элементов, иначе - null.</returns>
		public Interval<T>[] Exclude(Interval<T> value)
		{
			if (_first.CompareTo(value.First) < 0 && _last.CompareTo(value.Last) > 0)
				return new[] {new Interval<T>(_first, value.First), new Interval<T>(value.Last, _last)};
			T tmin = _first.CompareTo(value.First) < 0 ? _first : value.Last;
			T tmax = _last.CompareTo(value.Last) > 0 ? _last : value.First;
			return tmin.CompareTo(tmax) <= 0 ? new[] {new Interval<T>(tmin, tmax)} : null;
		}

		/// <summary>
		/// Исключает текущий интервал из набора интервалов <paramref name="values"/>.
		/// </summary>
		/// <param name="values">Набор интервалов.</param>
		/// <returns>Набор интервалов.</returns>
		public List<Interval<T>> Exclude(List<Interval<T>> values)
		{
			var curValues = new List<Interval<T>>(values.Count);
			foreach (var t in values)
			{
				if (IsIntersection(t))
				{
					Interval<T>[] cur = Exclude(t);
					if (cur != null)
						curValues.AddRange(cur);
				}
				else
					curValues.Add(t);
			}
			return curValues;
		}

		#endregion

		#region Внутренние поля

		private T _last;
		private T _first;

		private static int SorterByMin(Interval<T> a, Interval<T> b)
		{
			return a.First.CompareTo(b.First);
		}

		#endregion

		#region IEquatable<Interval<T>> Members

		public bool Equals(Interval<T> other)
		{
			if (other == null) return false;
			if (ReferenceEquals(this, other)) return true;
			return Equals(other._first, _first) && Equals(other._last, _last);
		}

		#endregion

		#region EqualityComparer

		public class IntervalEqualityComparer : EqualityComparer<Interval<T>>
		{
			public override bool Equals(Interval<T> x, Interval<T> y)
			{
				return x.Equals(y);
			}

			public override int GetHashCode(Interval<T> obj)
			{
				return obj.GetHashCode();
			}
		}

		#endregion

		public override bool Equals(object obj)
		{
			if (ReferenceEquals(null, obj)) return false;
			if (ReferenceEquals(this, obj)) return true;
			if (obj.GetType() != typeof(Interval<T>)) return false;
			return Equals((Interval<T>)obj);
		}

		public override int GetHashCode()
		{
			unchecked
			{
				return (_first.GetHashCode()*397) ^ _last.GetHashCode();
			}
		}

	    public static int GetDistance(Interval<int> first, Interval<int> second)
	    {
            if (first.First > second.Last) return first.First - second.Last;
            if (second.First > first.Last) return first.Last - second.First;

	        return 0;
	    }

        public static double GetDistance(Interval<double> first, Interval<double> second)
        {
            if (first.First > second.Last) return first.First - second.Last;
            if (second.First > first.Last) return first.Last - second.First;

            return 0;
        }
	}
}